#include "FSOperationClient.h"
#include "curl\curl.h"
#include "json\json.h"

using namespace std;

static HResult ParseJson(Json::Value value, OUT DIR_INFO& info)
{
    info._parentID._parts._userID = value["userid"].asUInt();
    info._parentID._parts._dirSN = value["parentid"].asUInt();
    strcpy(info._name, value["name"].asCString());
    info._dirID._parts._userID = info._parentID._parts._userID;
    info._dirID._parts._dirSN = value["dirid"].asUInt();
    info._fileCount = value["filecount"].asUInt();
    info._totalSize = value["totalsize"].asUInt();
    info._isDeleted = value["isdeleted"].asBool();
    info._createTime = value["createtime"].asUInt();
    info._deleteTime = value["deletetime"].asUInt();
    info._updateTime = value["updatetime"].asUInt();

    return ERROR_CODE_OK;
}

static HResult ParseJson(Json::Value value, OUT FILE_INFO& info)
{
    info._parentID._parts._userID = value["userid"].asUInt();
    info._parentID._parts._dirSN = value["parentid"].asUInt();
    strcpy(info._name, value["name"].asCString());
    info._isDeleted = value["isdeleted"].asBool();
    info._createTime = value["createtime"].asUInt();
    info._deleteTime = value["deletetime"].asUInt();
    info._compressType = value["compresstype"].asUInt();
    info._securityType = value["securitytype"].asUInt();
    info._length = value["length"].asUInt();
    info._dataCRC = value["datacrc"].asUInt();
    info._isSharing = value["issharing"].asBool();
    info._fileVersion = value["fileversion"].asUInt();
    info._headCRC = value["headcrc"].asUInt();

    return ERROR_CODE_OK;
}

size_t FSOperationClient::WriteCallback( void *ptr, size_t size, size_t nmemb, void *userdata)
{
    size_t nRealSize = size * nmemb;

    WriteDataStruct *data = (WriteDataStruct*)userdata;

    data->_data.append((char*)ptr, nRealSize);

    return nRealSize;
}

FSOperationClient::FSOperationClient(const char* svrHost, const char* logHost)
{
    _svrHost.assign(svrHost);
    _logHost.assign(logHost);
}

FSOperationClient::~FSOperationClient()
{
}

HResult FSOperationClient::MakeRequest(const char* query_url, OUT string& resData, const char* postData, size_t len)
{
    HResult ret = ERROR_CODE_OK;
    CURL *curl;
    CURLcode res;
    long retcode;
    WriteDataStruct writedata;

    curl = curl_easy_init();
    if (curl == NULL) 
    {
        fprintf(stderr, "can't init curl object\n");
        return ERROR_CODE_INTERNAL;
    }

    curl_easy_setopt(curl, CURLOPT_URL, query_url);
    if (postData != NULL)
    {
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData);
        
    }

    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, FSOperationClient::WriteCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&writedata);

    res = curl_easy_perform(curl);    
    if (res != CURLE_OK)
    {
        fprintf(stderr, "ERROR: %s, err: %s\n", query_url, strerror(res));
        goto done;
    }

    res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &retcode); 
    if (res != CURLE_OK)
    {
        fprintf(stderr, "ERROR: %s, err: %s\n", query_url, strerror(res));
        goto done;
    }

    if (retcode != 200)
    {
        fprintf(stderr, "execute request get error code: %s, return:%d\n", query_url, retcode);
        res = CURLE_OBSOLETE4;
        goto done;
    }
    
    double length;
    res = curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length); 
    if (res != CURLE_OK)
    {
        fprintf(stderr, "ERROR: %s, err: %s\n", query_url, strerror(res));
        goto done;
    }
    
    while(writedata._data.size() < (size_t)length)
    {
        Sleep(100);
    }

    // get data
    resData = writedata._data;

done:
    curl_easy_cleanup(curl);
    if (res != CURLE_OK)
    {
        ret = ERROR_CODE_INTERNAL;
    }
    return ret;
}

HResult FSOperationClient::WriteOpLog(const OpLog& log)
{
    static const char* cmd = "addlog.php";

    HResult ret = ERROR_CODE_OK;
    char url[MAX_URL];
    //http://host/userid/parentID/cmd?parameter
    _snprintf(url, MAX_URL, "http://%s/%s",
        _logHost.c_str(), cmd);

    string resData;
    string logdata = log.getData();
    ret = MakeRequest(url, resData, logdata.c_str(), logdata.size());

    if (ret != ERROR_CODE_OK)
    {
        fprintf(stderr, "Write operation log Failed, err:%d, data: %s\n",
            ret, resData.c_str());
        return ret;
    }
    return ret;
}

HResult FSOperationClient::GetSubdirInfo(DIR_ID parentID, const std::string& dirName, OUT DIR_INFO& dirInfo)
{
    static const char* cmd = "dirinfo";

    HResult ret = ERROR_CODE_OK;
    char url[MAX_URL];
    //http://host/userid/parentID/cmd?parameter
    _snprintf(url, MAX_URL, "http://%s/%u/%u/%s?name=%s",
        _svrHost.c_str(), parentID._parts._userID, parentID._parts._dirSN,
        cmd, dirName.c_str());
    string resData;
    ret = MakeRequest(url, resData);
    
    if (ret != ERROR_CODE_OK)
    {
        fprintf(stderr, "Get Dir Info request Failed, err:%d, data: %s\n",
            ret, resData.c_str());
    }
    
    //parse json result
    Json::Reader reader;
    Json::Value value;
    if (reader.parse(resData.c_str(), value))
    {
        ret = value["result"].asInt();
        if (ret != ERROR_CODE_OK)
        {
            fprintf(stderr, "Get Dir Info Failed, err:%d, data: %s\n",
                    ret, resData.c_str());
            return ret;
        }
        
        Json::Value jsonInfo = value["data"];
        ParseJson(jsonInfo, dirInfo);
    }
    else
    {
        fprintf(stderr, "parse json failed: %s\n", resData.c_str());
        ret = ERROR_CODE_INTERNAL;
    }

    return ret;
}

HResult FSOperationClient::GetSubdirInfos(DIR_ID parentID, OUT std::vector<DIR_INFO>& dirInfos)
{
    static const char* cmd = "lsdir";

    HResult ret = ERROR_CODE_OK;
    char url[MAX_URL];
    //http://host/userid/parentID/cmd?parameter
    _snprintf(url, MAX_URL, "http://%s/%u/%u/%s?mode=1",
        _svrHost.c_str(), parentID._parts._userID, parentID._parts._dirSN,
        cmd);
    string resData;
    ret = MakeRequest(url, resData);
    if (ret != ERROR_CODE_OK)
    {
        fprintf(stderr, "List Dir Failed, err:%d, data: %s\n",
            ret, resData.c_str());
        return ret;
    } 

    //parse json result
    Json::Reader reader;
    Json::Value value;
    if (reader.parse(resData.c_str(), value))
    {
        ret = value["result"].asInt();
        if (ret != ERROR_CODE_OK)
        {
            return ret;
        }

        Json::Value arrayInfo = value["array"];
        for (size_t idx = 0; idx < arrayInfo.size(); idx++)
        {
            DIR_INFO info;
            ParseJson(arrayInfo[idx], info);
            dirInfos.push_back(info);
        }
    }
    else
    {
        fprintf(stderr, "parse json failed: %s\n", resData.c_str());
        ret = ERROR_CODE_INTERNAL;
    }

    return ret;
}

HResult FSOperationClient::GetSubfileInfo(DIR_ID parentID, const std::string& fileName, OUT FILE_INFO& fileInfo)
{
    static const char* cmd = "fileinfo";

    HResult ret = ERROR_CODE_OK;
    char url[MAX_URL];
    //http://host/userid/parentID/cmd?parameter
    _snprintf(url, MAX_URL, "http://%s/%u/%u/%s?name=%s",
        _svrHost.c_str(), parentID._parts._userID, parentID._parts._dirSN,
        cmd, fileName.c_str());
    string resData;
    ret = MakeRequest(url, resData);
    
    if (ret != ERROR_CODE_OK)
    {
        fprintf(stderr, "Get Dir Info request Failed, err:%d, data: %s\n",
            ret, resData.c_str());
    }
    
    //parse json result
    Json::Reader reader;
    Json::Value value;
    if (reader.parse(resData.c_str(), value))
    {
        ret = value["result"].asInt();
        if (ret != ERROR_CODE_OK)
        {
            fprintf(stderr, "Get Dir Info Failed, err:%d, data: %s\n",
                    ret, resData.c_str());
            return ret;
        }
        
        Json::Value jsonInfo = value["data"];
        ParseJson(jsonInfo, fileInfo);
    }
    else
    {
        fprintf(stderr, "parse json failed: %s\n", resData.c_str());
        ret = ERROR_CODE_INTERNAL;
    }

    return ret;
}

HResult FSOperationClient::GetSubfileInfos(DIR_ID parentID, OUT std::vector<FILE_INFO>& fileInfos)
{
    static const char* cmd = "lsdir";

    HResult ret = ERROR_CODE_OK;
    char url[MAX_URL];
    //http://host/userid/parentID/cmd?parameter
    _snprintf(url, MAX_URL, "http://%s/%u/%u/%s?mode=2",
        _svrHost.c_str(), parentID._parts._userID, parentID._parts._dirSN,
        cmd);
    string resData;
    ret = MakeRequest(url, resData);
    //TODO: parse json result
    if (ret != ERROR_CODE_OK)
    {
        fprintf(stderr, "List Dir Failed, err:%d, data: %s\n",
            ret, resData.c_str());
    }

    //parse json result
    Json::Reader reader;
    Json::Value value;
    if (reader.parse(resData.c_str(), value))
    {
        ret = value["result"].asInt();
        if (ret != ERROR_CODE_OK)
        {
            return ret;
        }

        Json::Value arrayInfo = value["array"];
        for (size_t idx = 0; idx < arrayInfo.size(); idx++)
        {
            FILE_INFO info;
            ParseJson(arrayInfo[idx], info);
            fileInfos.push_back(info);
        }
    }
    else
    {
        fprintf(stderr, "parse json failed: %s\n", resData.c_str());
        ret = ERROR_CODE_INTERNAL;
    }

    return ret;
}

HResult FSOperationClient::ReadFile(const FILE_INFO& sourceInfo, uint64_t offset, OUT int8_t* buf, size_t readCount, OUT size_t& actualReadCount)
{
    static const char* cmd = "readfile";

    HResult ret = ERROR_CODE_OK;
    char url[MAX_URL];
    //http://host/userid/parentID/cmd?parameter
    _snprintf(url, MAX_URL, "http://%s/%u/%u/%s?name=%s&offset=%u&length=%u",
        _svrHost.c_str(), sourceInfo._parentID._parts._userID,
        sourceInfo._parentID._parts._dirSN,
        cmd, sourceInfo._name, offset, readCount);

    string resData;
    ret = MakeRequest(url, resData);
    
    if (ret != ERROR_CODE_OK)
    {
        fprintf(stderr, "Read File Failed, err:%d, data: %s\n",
            ret, resData.c_str());
    }

    memcpy(buf, resData.c_str(), resData.size());
    actualReadCount = resData.size();
    return ret;
}

/* Write Operations */
HResult FSOperationClient::CreateNewDir(DIR_ID parentID, const std::string &newDirName, DIR_INFO &newDirInfo)
{
    static const char* cmd = "mkdir";

    HResult ret = ERROR_CODE_OK;
    char url[MAX_URL];
    //http://host/userid/parentID/cmd?parameter
    _snprintf(url, MAX_URL, "http://%s/%u/%u/%s?name=%s",
        _svrHost.c_str(), parentID._parts._userID, parentID._parts._dirSN,
        cmd, newDirName.c_str());
    string resData;
    ret = MakeRequest(url, resData);
    
    if (ret != ERROR_CODE_OK)
    {
        fprintf(stderr, "Create Dir Failed, err:%d, data: %s\n",
            ret, resData.c_str());
    }
    
    //parse json result
    Json::Reader reader;
    Json::Value value;
    if (reader.parse(resData.c_str(), value))
    {
        ret = value["result"].asInt();
        if (ret != ERROR_CODE_OK)
        {
            fprintf(stderr, "Create Dir Failed, err:%d, data: %s\n",
                    ret, resData.c_str());
            return ret;
        }
        
        Json::Value jsonInfo = value["data"];
        ParseJson(jsonInfo, newDirInfo);
    }
    else
    {
        fprintf(stderr, "parse json failed: %s\n", resData.c_str());
        ret = ERROR_CODE_INTERNAL;
    }

    return ret;
}

HResult FSOperationClient::CreateLinkDir(DIR_ID parentID, const std::string& newDirName, DIR_ID sourceID, OUT DIR_INFO& newDirInfo)
{
    HResult ret = ERROR_CODE_NOT_IMPLEMENT;

    return ret;
}

HResult FSOperationClient::RenameDir(DIR_ID parentID, const std::string& orgDirName, const std::string& newDirName, OUT DIR_INFO& newDirInfo)
{
    static const char* cmd = "rename_dir";

    HResult ret = ERROR_CODE_OK;
    char url[MAX_URL];
    //http://host/userid/parentID/cmd?parameter
    _snprintf(url, MAX_URL, "http://%s/%u/%u/%s?name=%s&newname=%s",
        _svrHost.c_str(), parentID._parts._userID, parentID._parts._dirSN,
        cmd, orgDirName.c_str(), newDirName.c_str());
    string resData;
    ret = MakeRequest(url, resData);
    
    if (ret != ERROR_CODE_OK)
    {
        fprintf(stderr, "Rename Dir Request Failed, err:%d, data: %s\n",
            ret, resData.c_str());
    }
    
    //parse json result
    Json::Reader reader;
    Json::Value value;
    if (reader.parse(resData.c_str(), value))
    {
        ret = value["result"].asInt();
        if (ret != ERROR_CODE_OK)
        {
            fprintf(stderr, "Rename Dir Failed, err:%d, data: %s\n",
                    ret, resData.c_str());
            return ret;
        }
        
        Json::Value jsonInfo = value["data"];
        ParseJson(jsonInfo, newDirInfo);
    }
    else
    {
        fprintf(stderr, "parse json failed: %s\n", resData.c_str());
        ret = ERROR_CODE_INTERNAL;
    }
    return ret;
}

HResult FSOperationClient::DeleteDir(DIR_ID parentID, const std::string& orgDirName) // in recursion 
{
    static const char* cmd = "rmdir";

    HResult ret = ERROR_CODE_OK;
    char url[MAX_URL];
    //http://host/userid/parentID/cmd?parameter
    _snprintf(url, MAX_URL, "http://%s/%u/%u/%s?name=%s",
        _svrHost.c_str(), parentID._parts._userID, parentID._parts._dirSN,
        cmd, orgDirName.c_str());

    string resData;
    ret = MakeRequest(url, resData);
    
    if (ret != ERROR_CODE_OK)
    {
        fprintf(stderr, "Delete Dir Request Failed, err:%d, data: %s\n",
            ret, resData.c_str());
    }
    return ret;
}

HResult FSOperationClient::CreateNewFile(DIR_ID parentID, const std::string& newFileName, const int8_t* buf, size_t bufLen, OUT FILE_INFO& newFileInfo)
{
    static const char* cmd = "writefile";

    HResult ret = ERROR_CODE_OK;
    char url[MAX_URL];
    //http://host/userid/parentID/cmd?parameter
    _snprintf(url, MAX_URL, "http://%s/%u/%u/%s?name=%s",
        _svrHost.c_str(), parentID._parts._userID, parentID._parts._dirSN,
        cmd, newFileName.c_str());

    string resData;
    ret = MakeRequest(url, resData, (const char*)buf, bufLen);
    
    if (ret != ERROR_CODE_OK)
    {
        fprintf(stderr, "Write File Request Failed, err:%d, data: %s\n",
            ret, resData.c_str());
    }

    //parse json result
    Json::Reader reader;
    Json::Value value;
    if (reader.parse(resData.c_str(), value))
    {
        ret = value["result"].asInt();
        if (ret != ERROR_CODE_OK)
        {
            fprintf(stderr, "Write New File Failed, err:%d, data: %s\n",
                    ret, resData.c_str());
            return ret;
        }
        
        Json::Value jsonInfo = value["data"];
        ParseJson(jsonInfo, newFileInfo);
    }
    else
    {
        fprintf(stderr, "parse json failed: %s\n", resData.c_str());
        ret = ERROR_CODE_INTERNAL;
    }
    return ret;
}

HResult FSOperationClient::CreateNewFile(DIR_ID parentID, const std::string& newFileName, const std::wstring& localFileName, OUT FILE_INFO& newFileInfo)
{
    //TODO: change to stream model
    FILE *fp = _wfopen(localFileName.c_str(), L"rb");
    if (fp == NULL)
    {
        fwprintf(stderr, L"can't open local file: %s", localFileName.c_str());
        return ERROR_CODE_IOERROR;
    }

    fseek(fp, 0, SEEK_END);
    long len = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    int8_t* buf = new int8_t[len];
    fread(buf, sizeof(int8_t), len, fp);
    fclose(fp);
    
    HResult ret = CreateNewFile(parentID, newFileName, buf, len, newFileInfo);
    
    delete[] buf;
    return ret;
}

HResult FSOperationClient::CreateLinkFile(DIR_ID parentID, const std::string& newFileName, const FILE_INFO& sourceInfo, OUT FILE_INFO& newFileInfo)
{
    HResult ret = ERROR_CODE_NOT_IMPLEMENT;

    return ret;
}

HResult FSOperationClient::RenameFile(DIR_ID parentID, const std::string& orgFileName, const std::string& newFileName, OUT FILE_INFO& newFileInfo)
{
    static const char* cmd = "rename_file";

    HResult ret = ERROR_CODE_OK;
    char url[MAX_URL];
    //http://host/userid/parentID/cmd?parameter
    _snprintf(url, MAX_URL, "http://%s/%u/%u/%s?name=%s&newname=%s",
        _svrHost.c_str(), parentID._parts._userID, parentID._parts._dirSN,
        cmd, orgFileName.c_str(), newFileName.c_str());
    string resData;
    ret = MakeRequest(url, resData);
    
    if (ret != ERROR_CODE_OK)
    {
        fprintf(stderr, "Rename File Request Failed, err:%d, data: %s\n",
            ret, resData.c_str());
    }
    
    //parse json result
    Json::Reader reader;
    Json::Value value;
    if (reader.parse(resData.c_str(), value))
    {
        ret = value["result"].asInt();
        if (ret != ERROR_CODE_OK)
        {
            fprintf(stderr, "Rename File Failed, err:%d, data: %s\n",
                    ret, resData.c_str());
            return ret;
        }
        
        Json::Value jsonInfo = value["data"];
        ParseJson(jsonInfo, newFileInfo);
    }
    else
    {
        fprintf(stderr, "parse json failed: %s\n", resData.c_str());
        ret = ERROR_CODE_INTERNAL;
    }
    return ret;
}

HResult FSOperationClient::DeleteAFile(DIR_ID parentID, const std::string& orgFileName)
{
    static const char* cmd = "rmfile";

    HResult ret = ERROR_CODE_OK;
    char url[MAX_URL];
    //http://host/userid/parentID/cmd?parameter
    _snprintf(url, MAX_URL, "http://%s/%u/%u/%s?name=%s",
        _svrHost.c_str(), parentID._parts._userID, parentID._parts._dirSN,
        cmd, orgFileName.c_str());
    string resData;
    ret = MakeRequest(url, resData);
    
    if (ret != ERROR_CODE_OK)
    {
        fprintf(stderr, "Delete File Failed, err:%d, data: %s\n",
            ret, resData.c_str());
    }
    return ret;
}